<?php
namespace Pinenut;

use Pinenut\Service\UrlManager;
use Pinenut\Response\LiteException;
use Pinenut\Response\Exception;

/**
 * Базовый класс приложения
 *
 * Реализует обработку запроса
 *
 * @author Kosh
 *        
 */
abstract class Application {
	
	use\Pinenut\Di\Injectable;

	/**
	 *
	 * @var \Pinenut\Log\File
	 */
	// static $LogClass = '\Pinenut\Log\File';
	protected static $libraryPath;

	protected static $applicationPath;

	protected static $isDebug = false;

	protected static $isConsole = false;

	protected static $isTest = false;

//	protected static $environment;

	protected static $logFile;

	protected static $modules = [];

	/**
	 * Исполняется приложение
	 */
	static function run( ) {
		// устанавливаем окружение
		static::prepareEnv();
		// подключаем модули
		static::prepareModules();
		
		// настраивает зависимости
		new Di('\DiConfig');
		// устанавливаем обработчик ошибок
		static::setErrorHandler();
		
		try {
			\Log::info('BEGIN', 'OK');
			\Log::begin(null, 'Application');
			// запускаем маршрутизацию, получаем запрос и исполняем действие с ним
			$request = static::route();
			
			// если не смогли сформировать запрос - корректно отваливаемся
			if (!$request) {
				throw new \Exception('Page not found', 404);
			}
			
			\Log::end('Route');
			\Log::begin(null, 'Application');
				
			$context = static::context($request);
			$response = static::dispatch($context);

			\Log::end('Dispatch');
				
			\Log::onFinish();
			
			static::out($response);
			\Log::info('END', 'OK');
		} catch (Exception $e) {
			$e = $e->getLiteException();
			\Log::error('CRASH', "Message: {$e->getMessage()}\nTrace: {$e->getTraceAsString()}\n");
			static::errorHandler(1, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getCode(), $e->getTrace());
		} catch (\Exception $e) {
			\Log::error('CRASH', "Message: {$e->getMessage()}\nTrace: {$e->getTraceAsString()}\n");
			static::errorHandler(1, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getCode(), $e->getTrace());
		}
	}

	static function out(\Pinenut\Response $response) {
		if (!static::isConsole()) {
			// выводим заголовки
			$headers = $response->headers();
			foreach ($headers as $header) {
				header($header[0], $header[1], (int)$header[2]);
			}
		}
		// выводи контент
		echo $response->content;
	}
	
	
	/**
	 * Получаем строку окружения и устанавливаем эту зависимость в конфигурацию
	 * Окружение берётся или из переменной (попадает из nginx), если не находим
	 * то ищем в корне проекта файл .env и берём его содержимое как окружение
	 */
	static function prepareEnv() {
		$env = isset($_SERVER['PINENUT_ENV'])?$_SERVER['PINENUT_ENV']:null;
		if (empty($env)) {
			$path = static::applicationPath().'/../.env';
			if (file_exists($path)) {
				$env = file_get_contents($path);
			}
		}
		Config::$environment = $env;
	}

	/**
	 * Подключаем модули
	 *
	 * Настраиваем include paths
	 * Создаём массив путей для поиска вьюх
	 *
	 * TODO: отслеживание версий модулей, подключение зависимостей и т.д.
	 * TODO: список путей где могут быть модули и поиск в них модулей... но что-то как-то геморрно и долго каждый раз
	 * TODO: вынести отсюда этот код в какой-нить pinenut/modules
	 */
	static function prepareModules( ) {
		$modulePath = static::$libraryPath . '/../modules/';
		$baseApplicationPath = static::$libraryPath . '/../application/';
		$paths = [];
		$isConsole = static::isConsole();
		$isTest = static::isTest();
		foreach (static::$modules as $module) {
			array_unshift($paths, $modulePath . $module . '/library');
			array_unshift($paths, $modulePath . $module . '/application');
			// если у нас консоль - включаем её путь самым первым
			if ($isConsole) {
				array_unshift($paths, $modulePath . $module . '/console');
			}
			// если у нас тест - включаем её путь самым первым
			if ($isTest) {
				array_unshift($paths, $modulePath . $module . '/tests');
			}
		}
		array_push($paths, $baseApplicationPath);
		array_push($paths, static::$libraryPath);
		array_unshift($paths, static::$applicationPath);
		
		// если у нас консоль - включаем её путь самым первым
		if (static::isConsole()) {
			array_unshift($paths, static::$applicationPath . '/../console');
		}
		
		set_include_path(implode(PATH_SEPARATOR, $paths));
	}

	static protected function route( ) {
		list ($url) = explode('?', $_SERVER["REQUEST_URI"]);
		return (new \UrlManager())->parseUrl($url, $_SERVER['REQUEST_METHOD'], 
			$_SERVER['HTTP_HOST'], $_REQUEST);
	}

	static protected function context(\Pinenut\Request $request) {
		return new \Context($request);
	}

	static protected function dispatch($context) {
		return \Dispatcher::dispatch($context);
	}

	static function setErrorHandler( ) {
		set_error_handler(array(
			'static',
			'errorHandler'
		));
		register_shutdown_function(
			array(
				'\Application',
				'shutDownFunction'
			));
		ini_set('display_errors', 0);
	}

	static function shutDownFunction( ) {
		$error = error_get_last();
		if (! ($error['type'] & 8)) {
			static::errorHandler($error['type'], $error['message'], 
				$error['file'], $error['line']);
		}
	}

	static function errorHandler($errno, $errstr, $errfile, $errline, 
		$code = 503, $trace = null) {
		if (! (error_reporting() & $errno)) {
			// This error code is not included in error_reporting
			return;
		}
		$error = array(
			'type' => $errno,
			'message' => $errstr,
			'file' => $errfile,
			'line' => $errline,
			'code' => $code
		);
		if (null === $trace) {
			$trace = debug_backtrace();
		}
		// Логгируем ошибку
		
		// грохае буфер того, что вывели ранее
		@ob_end_clean();
		
		// Выводим
		$request = new \Request('errorHandler', 
			array(
				'error' => $error,
				'trace' => $trace,
				'mainRequest' => is_object(\Dispatcher::$currentContext) ? \Dispatcher::$currentContext->getMainRequest() : null
			), null, false);
		
		$request->isRoot(true);
		$request->isRenderLayout(true);
		
		$context = static::context($request);
		
		$response = static::dispatch($context);
		
		static::out($response);
		
		return true;
		// exit;
	}

	static function libraryPath($path = null) {
		if (null !== $path)
			static::$libraryPath = $path;
		return static::$libraryPath;
	}

	static function applicationPath($path = null) {
		if (null !== $path)
			static::$applicationPath = $path;
		return static::$applicationPath;
	}

	static function isDebug($param = null) {
		if (null !== $param)
			static::$isDebug = $param;
		return static::$isDebug;
	}

	static function isConsole($param = null) {
		if (null !== $param)
			static::$isConsole = $param;
		return static::$isConsole;
	}

	static function isTest($param = null) {
		if (null !== $param)
			static::$isTest = $param;
		return static::$isTest;
	}

// 	static function environment($str = null) {
// 		if (null !== $str)
// 			static::$environment = $str;
// 		return static::$environment;
// 	}

	static function logFile($name = null) {
		if (null !== $name)
			static::$logFile = $name;
		return static::$logFile;
	}

	/**
	 * Возвращаем объект аутентификации текущего контекста
	 * 
	 * @return \Pinenut\Context\Auth
	 */
	static function auth( ) {
		return \Dispatcher::$currentContext->auth;
	}
}